using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Columbus.Tests.Features.Pages;
using Columbus.Tests.Testing;
using Microsoft.Phone.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Columbus.Tests.Features
{
    // ReSharper disable InconsistentNaming
    // BDD style naming
    public class ViewModelFixture
    {
        public class GivenViewModelWithDefaultDeactivationBehavior
        {
            [TestClass]
            public class WhenViewModelDeactivated
            {
                private FixtureViewModel viewModel;
                private ViewState viewState;

                [TestInitialize]
                public void SetUp()
                {
                    viewModel = new FixtureViewModel();
                    viewState = new ViewState(String.Empty, String.Empty);
                    viewModel.Deactivate(viewState);
                }

                [TestMethod]
                public void ThenNoneOfViewModelBaseClassPublicPropertiesSaved()
                {
                    foreach (var property in typeof(ViewModel).GetProperties())
                    {
                        foreach (var key in viewState.PropertyNames)
                        {
                            Assert.AreNotEqual(key, property.GetViewStateKey(), String.Format("Property {0} should not be restorable", property.Name));
                        }
                    }
                }

                [TestMethod]
                public void ThenAllPublicPropertiesWithPublicAccessorsNamesSaved()
                {
                    Assert.IsTrue(viewState.PropertyNames.All(n => n == viewModel.GetType().GetProperty("PublicTextPropertyOne").GetViewStateKey()
                        || n == viewModel.GetType().GetProperty("PublicNumericPropertyOne").GetViewStateKey()), 
                        "Unexpected properties saved");
                }

                [TestMethod]
                public void ThenAllPublicPropertiesWithPublicAccessorsValuesSaved()
                {
                    Assert.AreEqual(viewModel.PublicNumericPropertyOne,
                                    viewState.Get(viewModel.GetType().GetProperty("PublicNumericPropertyOne").GetViewStateKey()));
                    Assert.AreEqual(viewModel.PublicTextPropertyOne,
                                    viewState.Get(viewModel.GetType().GetProperty("PublicTextPropertyOne").GetViewStateKey()));

                }
            }

            [TestClass]
            public class WhenViewModelActivated
            {
                private FakeViewModel viewModel;
                private IViewState viewState;
                private IApplicationSettings applicationSettings;

                [TestInitialize]
                public void SetUp()
                {
                    viewModel = new FakeViewModel();
                    viewState = new FakeViewState();
                    applicationSettings = new FakeApplicationSettings();
                    viewModel.Activate(viewState, applicationSettings);
                }

                [TestMethod]
                public void ThenNoneOfViewModelBaseClassPublicPropertiesExceptSettingsRestored()
                {
                    foreach (var property in typeof(ViewModel).GetProperties().Where(p=>p.Name != "ApplicationSettings"))
                    {
                        var getMethod = property.GetGetMethod();
                        if (getMethod != null)
                            Assert.IsNull(property.GetValue(viewModel, null));
                    }
                }

                [TestMethod]
                public void ThenPersistedPropertiesRestored()
                {
                    Assert.AreEqual(1, viewModel.NumericProperty);
                }

                public class FakeViewState : IViewState
                {
                    public void SaveConstructorParameter(string key, object value)
                    {
                        throw new NotSupportedException();
                    }

                    public void Save(string key, object value)
                    {
                        throw new NotSupportedException();
                    }

                    public object Get(string key)
                    {
                        return 1;
                    }

                    public void Persist(ITransientStore transientStore)
                    {
                        throw new NotSupportedException();
                    }
                }

                public class FakeViewModel : ViewModel
                {
                    public int NumericProperty { get; set; }
                }
            }

            [TestClass]
            public class WhenChooserTaskLaunched
            {
                private bool chooserShown;
                private FixtureViewModel viewModel;
                private readonly PhotoResult result = new PhotoResult();
                private NavigationAppication applicationOne;
                private readonly Func<string, FrameworkElement> viewLocator = s =>
                {
                    Assert.AreEqual(viewName, s);
                    return new EmptyPage();
                };
                private const string viewName = "view1";

                [TestInitialize]
                public void SetUp()
                {
                    chooserShown = false;
                    Background.Execute = action => action();
                    viewModel = new FixtureViewModel();
                    applicationOne = NavigationApplicationExtensions.CreateNewAppWithOneNavigatorWithViewLocator(viewLocator);
                    applicationOne.StartNormally();
                    Chooser.Show = action => chooserShown = true;
                    Chooser.LastResult = new ChooserResult(result, applicationOne.GetNavigator(), typeof(FixtureViewModel).GetMethod("ProcessCameraCaptureResult"), typeof(FixtureViewModel));
                    applicationOne.GetNavigator().NavigateToView(viewName, viewModel);
                    viewModel.CameraCapture();
                    applicationOne.Deactivate();
                    applicationOne.Activate();
                    // ReSharper disable PossibleNullReferenceException
                    viewModel = (applicationOne.GetNavigator().NavigationFrame.Content as FrameworkElement).DataContext as FixtureViewModel;
                    // ReSharper restore PossibleNullReferenceException
                }

                [TestMethod]
                public void ThenShowsChooserProgram()
                {
                    Assert.IsTrue(chooserShown);
                }

                [TestMethod]
                public void ThenCallsRegisteredOnCompleteMethodPassingChooserResult()
                {
                    Assert.IsTrue(viewModel.LauncherCallbackInvoked);
                    Assert.AreEqual(result, viewModel.ResultOfLauncher);
                }

                [TestMethod]
                public void IfRegisteredOnCompleteIsNotMethodThenThrowsException()
                {
                    AssertExtensions.Throws<InvalidChooserOnCompleteHandlerException>(viewModel.CameraCaptureNotMethod);
                }

                [TestMethod]
                public void IfRegisteredOnCompleteMethodIsNotCurrentViewModelMethodThenThrowsException()
                {
                    AssertExtensions.Throws<InvalidChooserOnCompleteHandlerException>(viewModel.CameraCaptureNotThisClass);
                }

                [TestMethod]
                public void IfRegisteredOnCompleteMethodIsPrivateThenThrowsException()
                {
                    AssertExtensions.Throws<InvalidChooserOnCompleteHandlerException>(viewModel.CameraCapturePrivate);
                }

                [TestMethod]
                public void IfRegisteredOnCompleteMethodIsStaticThenThrowsException()
                {
                    AssertExtensions.Throws<InvalidChooserOnCompleteHandlerException>(viewModel.CameraCaptureStatic);
                }

                [TestMethod]
                public void IfRegisteredOnCompleteMethodDoesNotAcceptParametersThenThrowsException()
                {
                    AssertExtensions.Throws<InvalidChooserOnCompleteHandlerException>(viewModel.CameraCaptureWrongParameterMethod);
                }

                [TestMethod]
                public void IfRegisteredOnCompleteMethodAcceptMoreThanOneParameterThenThrowsException()
                {
                    AssertExtensions.Throws<InvalidChooserOnCompleteHandlerException>(viewModel.CameraCaptureManyParametersMethod);
                }

                [TestMethod]
                public void IfRegisteredOnCompleteMethodAcceptParameterOfWrongTypeThenThrowsException()
                {
                    AssertExtensions.Throws<InvalidChooserOnCompleteHandlerException>(viewModel.CameraCaptureWrongTypeParametersMethod);
                }

                [TestCleanup]
                public void CleanUp()
                {
                    Background.ResetToDefaults();
                    Chooser.ResetToDefaults();
                }
            }

            public class FixtureViewModel : ViewModel
            {
                private string publicTextPropertyOne = "publicTextPropertyOne";
                public string PublicTextPropertyOne
                {
                    get { return publicTextPropertyOne; }
                    set { publicTextPropertyOne = value; }
                }

                private int publicNumericPropertyOne = 1;
                public int PublicNumericPropertyOne
                {
                    get { return publicNumericPropertyOne; }
                    set { publicNumericPropertyOne = value; }
                }

                // ReSharper disable UnusedAutoPropertyAccessor.Local
                public PhotoResult SomeResult { get; private set; }
                // ReSharper restore UnusedAutoPropertyAccessor.Local

                public void CameraCaptureNotThisClass()
                {
                    var anotherViewModel = new AnotherViewModel();
                    LaunchTask<CameraCaptureTask, PhotoResult>(result => anotherViewModel.ProcessCameraCaptureResult(result));
                }

                public void CameraCaptureNotMethod()
                {
                    LaunchTask<CameraCaptureTask, PhotoResult>(result => new PhotoResult());
                }

                public void CameraCaptureManyParametersMethod()
                {
                    LaunchTask<CameraCaptureTask, PhotoResult>(result => ProcessCameraCaptureMany(result, result));
                }

                public void CameraCaptureWrongTypeParametersMethod()
                {
                    LaunchTask<CameraCaptureTask, PhotoResult>(result => ProcessCameraCaptureWrongParamType("x"));
                }

                public void CameraCaptureWrongParameterMethod()
                {
                    LaunchTask<CameraCaptureTask, PhotoResult>(result => ProcessCameraCaptureResultEmpty());
                }

                public void CameraCaptureStatic()
                {
                    LaunchTask<CameraCaptureTask, PhotoResult>(result => ProcessCameraCaptureResultStatic(result));
                }

                public void CameraCapturePrivate()
                {
                    LaunchTask<CameraCaptureTask, PhotoResult>(result => PrivateProcessCameraCaptureResult(result));
                }

                public void CameraCapture()
                {
                    LaunchTask<CameraCaptureTask, PhotoResult>(result => ProcessCameraCaptureResult(result));
                }

                public static void ProcessCameraCaptureResultStatic(PhotoResult result)
                {
                }

                public void ProcessCameraCaptureResultEmpty()
                {
                }

                public void ProcessCameraCaptureMany(PhotoResult result1, PhotoResult result2)
                {
                }

                public void ProcessCameraCaptureWrongParamType(string result1)
                {
                }

                public void ProcessCameraCaptureResult(PhotoResult result)
                {
                    ResultOfLauncher = result;
                    LauncherCallbackInvoked = true;
                }

                // ReSharper disable UnusedMember.Local
                // ReSharper disable UnusedParameter.Local
                // ReSharper disable MemberCanBeMadeStatic.Local
                private void PrivateProcessCameraCaptureResult(PhotoResult result)
                // ReSharper restore MemberCanBeMadeStatic.Local
                // ReSharper restore UnusedParameter.Local
                // ReSharper restore UnusedMember.Local
                {
                }

                public PhotoResult ResultOfLauncher { get; private set; }

                public bool LauncherCallbackInvoked { get; private set; }

                // ReSharper disable UnusedMember.Local
                private string PrivateTextProperty { get; set; }
                // ReSharper restore UnusedMember.Local

                // ReSharper disable UnusedAutoPropertyAccessor.Local
                public string PublicTextPropertyWithPrivateGetter { private get; set; }
                // ReSharper restore UnusedAutoPropertyAccessor.Local

                // ReSharper disable UnusedAutoPropertyAccessor.Local
                public string PublicTextPropertyWithPrivateSetter { get; private set; }
                // ReSharper restore UnusedAutoPropertyAccessor.Local
            }

            public class AnotherViewModel : ViewModel
            {
                public void ProcessCameraCaptureResult(PhotoResult result)
                {
                    
                }
            }
        }
    }

    public class FakeApplicationSettings : IApplicationSettings
    {
        private readonly IDictionary<string, object> transientStore = new Dictionary<string, object>();

        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
        {
            return transientStore.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public void Clear()
        {
            transientStore.Clear();
        }

        public bool IsEditing
        {
            get { return false; }
        }

        public bool Contains(KeyValuePair<string, object> item)
        {
            return transientStore.Contains(item);
        }

        public int Count
        {
            get { return transientStore.Count; }
        }

        public void Add(string key, object value)
        {
            transientStore.Add(key, value);
        }

        public bool Contains(string key)
        {
            return transientStore.ContainsKey(key);
        }

        public bool Remove(string key)
        {
            return transientStore.Remove(key);
        }

        public bool TryGetValue(string key, out object value)
        {
            return transientStore.TryGetValue(key, out value);
        }

        public object this[string key]
        {
            get { return transientStore[key]; }
            set { transientStore[key] = value; }
        }

        public ICollection<string> Keys
        {
            get { return transientStore.Keys; }
        }

        public ICollection<object> Values
        {
            get { return transientStore.Values; }
        }

        public void BeginEdit()
        {
            
        }

        public void EndEdit()
        {
        }

        public void CancelEdit()
        {
        }
    }

    // ReSharper restore InconsistentNaming
}